# frozen_string_literal: true

describe Grape::DSL::Routing do
  subject { dummy_class }

  let(:dummy_class) do
    Class.new do
      extend Grape::DSL::Routing
      extend Grape::DSL::Settings
      extend Grape::DSL::Validations

      class << self
        attr_reader :instance, :base
        attr_accessor :configuration
      end
    end
  end

  let(:proc) { -> {} }
  let(:options) { { a: :b } }
  let(:path) { '/dummy' }

  describe '.version' do
    it 'sets a version for route' do
      version = 'v1'
      expect(subject.version(version)).to eq(version)
      expect(subject.inheritable_setting.namespace_inheritable[:version]).to eq([version])
      expect(subject.inheritable_setting.namespace_inheritable[:version_options]).to eq(using: :path)
    end
  end

  describe '.prefix' do
    it 'sets a prefix for route' do
      prefix = '/api'
      subject.prefix prefix
      expect(subject.inheritable_setting.namespace_inheritable[:root_prefix]).to eq(prefix)
    end
  end

  describe '.scope' do
    let(:root_app) do
      Class.new(Grape::API) do
        scope :my_scope do
          get :my_endpoint do
            return_no_content
          end
        end
      end
    end

    it 'create a scope without affecting the URL' do
      env = Rack::MockRequest.env_for('/my_endpoint', method: Rack::GET)
      response = Rack::MockResponse[*root_app.call(env)]
      expect(response).to be_no_content
    end
  end

  describe '.do_not_route_head!' do
    it 'sets do not route head option' do
      subject.do_not_route_head!
      expect(subject.inheritable_setting.namespace_inheritable[:do_not_route_head]).to be(true)
    end
  end

  describe '.do_not_route_options!' do
    it 'sets do not route options option' do
      subject.do_not_route_options!
      expect(subject.inheritable_setting.namespace_inheritable[:do_not_route_options]).to be(true)
    end
  end

  describe '.mount' do
    it 'mounts on a nested path' do
      subject = Class.new(Grape::API)
      app1 = Class.new(Grape::API)
      app2 = Class.new(Grape::API)
      app2.get '/nice' do
        'play'
      end

      subject.mount app1 => '/app1'
      app1.mount app2 => '/app2'

      expect(subject.inheritable_setting.to_hash[:namespace]).to eq({})
      expect(subject.inheritable_setting.to_hash[:namespace_inheritable]).to eq({})
      expect(app1.inheritable_setting.to_hash[:namespace_stackable]).to eq(mount_path: ['/app1'])

      expect(app2.inheritable_setting.to_hash[:namespace_stackable]).to eq(mount_path: ['/app1', '/app2'])
    end

    it 'mounts multiple routes at once' do
      base_app = Class.new(Grape::API)
      app1     = Class.new(Grape::API)
      app2     = Class.new(Grape::API)
      base_app.mount(app1 => '/app1', app2 => '/app2')

      expect(app1.inheritable_setting.to_hash[:namespace_stackable]).to eq(mount_path: ['/app1'])
      expect(app2.inheritable_setting.to_hash[:namespace_stackable]).to eq(mount_path: ['/app2'])
    end
  end

  describe '.route' do
    before do
      allow(subject).to receive(:endpoints).and_return([])
      allow(subject.inheritable_setting).to receive(:route_end)
      allow(subject).to receive(:reset_validations!)
    end

    it 'marks end of the route' do
      expect(subject.inheritable_setting).to receive(:route_end)
      subject.route(:any)
    end

    it 'resets validations' do
      expect(subject).to receive(:reset_validations!)
      subject.route(:any)
    end

    it 'defines a new endpoint' do
      expect { subject.route(:any) }
        .to change { subject.endpoints.count }.from(0).to(1)
    end

    it 'does not duplicate identical endpoints' do
      subject.route(:any)
      expect { subject.route(:any) }
        .not_to change(subject.endpoints, :count)
    end

    it 'generates correct endpoint options' do
      subject.inheritable_setting.route[:description] = { fiz: 'baz' }
      subject.inheritable_setting.namespace_stackable[:params] = { nuz: 'naz' }

      expect(Grape::Endpoint).to receive(:new) do |_inheritable_setting, endpoint_options|
        expect(endpoint_options[:method]).to eq :get
        expect(endpoint_options[:path]).to eq '/foo'
        expect(endpoint_options[:for]).to eq subject
        expect(endpoint_options[:route_options]).to eq(foo: 'bar', fiz: 'baz', params: { nuz: 'naz' })
      end.and_yield

      subject.route(:get, '/foo', { foo: 'bar' }, &proc {})
    end
  end

  describe '.get' do
    it 'delegates to .route' do
      expect(subject).to receive(:route).with(Rack::GET, path, options)
      subject.get path, **options, &proc
    end
  end

  describe '.post' do
    it 'delegates to .route' do
      expect(subject).to receive(:route).with(Rack::POST, path, options)
      subject.post path, **options, &proc
    end
  end

  describe '.put' do
    it 'delegates to .route' do
      expect(subject).to receive(:route).with(Rack::PUT, path, options)
      subject.put path, **options, &proc
    end
  end

  describe '.head' do
    it 'delegates to .route' do
      expect(subject).to receive(:route).with(Rack::HEAD, path, options)
      subject.head path, **options, &proc
    end
  end

  describe '.delete' do
    it 'delegates to .route' do
      expect(subject).to receive(:route).with(Rack::DELETE, path, options)
      subject.delete path, **options, &proc
    end
  end

  describe '.options' do
    it 'delegates to .route' do
      expect(subject).to receive(:route).with(Rack::OPTIONS, path, options)
      subject.options path, **options, &proc
    end
  end

  describe '.patch' do
    it 'delegates to .route' do
      expect(subject).to receive(:route).with(Rack::PATCH, path, options)
      subject.patch path, **options, &proc
    end
  end

  describe '.namespace' do
    it 'creates a new namespace with given name and options' do
      subject.namespace(:foo, foo: 'bar') {}
      expect(subject.namespace(:foo, foo: 'bar')).to eq(Grape::Namespace.new(:foo, foo: 'bar'))
    end

    it 'calls #joined_space_path on Namespace' do
      inside_namespace = nil
      subject.namespace(:foo, foo: 'bar') do
        inside_namespace = namespace
      end
      expect(inside_namespace).to eq('/foo')
    end
  end

  describe '.group' do
    it 'is alias to #namespace' do
      expect(subject.method(:group)).to eq subject.method(:namespace)
    end
  end

  describe '.resource' do
    it 'is alias to #namespace' do
      expect(subject.method(:resource)).to eq subject.method(:namespace)
    end
  end

  describe '.resources' do
    it 'is alias to #namespace' do
      expect(subject.method(:resources)).to eq subject.method(:namespace)
    end
  end

  describe '.segment' do
    it 'is alias to #namespace' do
      expect(subject.method(:segment)).to eq subject.method(:namespace)
    end
  end

  describe '.routes' do
    let(:main_app) { Class.new(Grape::API) }
    let(:first_app) { Class.new(Grape::API) }
    let(:second_app) { Class.new(Grape::API) }

    before do
      main_app.mount(first_app => '/first_app', second_app => '/second_app')
    end

    it 'returns flatten endpoints routes' do
      expect(main_app.endpoints).not_to be_empty
      expect(main_app.routes).to eq(main_app.endpoints.map(&:routes).flatten)
    end

    context 'when #routes was already called once' do
      it 'memoizes' do
        object_id = main_app.routes.object_id
        expect(main_app.routes.object_id).to eq(object_id)
      end
    end
  end

  describe '.route_param' do
    let!(:options) { { requirements: regex } }
    let(:regex) { /(.*)/ }

    it 'calls #namespace with given params' do
      expect(subject).to receive(:namespace).with(':foo', {}).and_yield
      subject.route_param('foo', &proc {})
    end

    it 'nests requirements option under param name' do
      expect(subject).to receive(:namespace) do |_param, options|
        expect(options[:requirements][:foo]).to eq regex
      end
      subject.route_param('foo', **options, &proc {})
    end

    it 'does not modify options parameter' do
      allow(subject).to receive(:namespace)
      expect { subject.route_param('foo', **options, &proc {}) }
        .not_to(change { options })
    end
  end

  describe '.versions' do
    it 'returns last defined version' do
      subject.version 'v1'
      subject.version 'v2'
      expect(subject.version).to eq('v2')
    end
  end
end
